﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace NMF.Expressions
{
    internal abstract class ObservableStaticProxyCallBase<TResult> : ObservableReversableExpression<TResult>
    {
        protected void ArgumentChanged(object sender, ValueChangedEventArgs e)
        {
            if (!IsAttached) return;
            RenewProxy();
            Refresh();
        }

        public INotifyValue<TResult> Proxy { get; private set; }

        protected override TResult GetValue()
        {
            return Proxy.Value;
        }

        public override void SetValue(TResult value)
        {
            var reversable = Proxy as IReversableNotifyValue<TResult>;
            reversable.Value = value;
        }

        public override bool IsReversable
        {
            get
            {
                if (Proxy == null) return true;
                var reversable = Proxy as IReversableNotifyValue<TResult>;
                return reversable != null && reversable.IsReversable;
            }
        }

        public void RenewProxy()
        {
            if (Proxy != null)
            {
                Proxy.ValueChanged -= ProxyValueChanged;
                IDisposable disposable = Proxy as IDisposable;
                if (disposable != null)
                {
                    disposable.Dispose();
                }
            }
            Proxy = CreateProxy();
            Proxy.Attach();
            Proxy.ValueChanged += ProxyValueChanged;
        }

        private void ProxyValueChanged(object sender, ValueChangedEventArgs e)
        {
            Refresh();
        }

        protected abstract INotifyValue<TResult> CreateProxy();

        protected override void DetachCore()
        {
            if (Proxy != null)
            {
                Proxy.Detach();
            }
        }

        protected override void AttachCore()
        {
            if (Proxy != null)
            {
                Proxy.Attach();
            }
            else
            {
                RenewProxy();
            }
        }

        public override ExpressionType NodeType
        {
            get
            {
                return ExpressionType.Extension;
            }
        }
    }

    internal sealed class ObservableStaticProxyCall<T1, TResult> : ObservableStaticProxyCallBase<TResult>
    {
        public ObservableStaticProxyCall(MethodCallExpression node, ObservableExpressionBinder binder, MethodInfo proxyMethod)
            : this((Func<T1, INotifyValue<TResult>>)proxyMethod.CreateDelegate(typeof(Func<T1, INotifyValue<TResult>>)),
                binder.VisitObservable<T1>(node.Arguments[0])) { }

        public ObservableStaticProxyCall(Func<T1, INotifyValue<TResult>> proxyFunction, IObservableExpression<T1> arg1)
        {
            if (proxyFunction == null) throw new ArgumentNullException("proxyFunction");
            if (arg1 == null) throw new ArgumentNullException("arg1");

            ProxyFunction = proxyFunction;
            Argument1 = arg1;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        public Func<T1, INotifyValue<TResult>> ProxyFunction { get; private set; }

        protected override INotifyValue<TResult> CreateProxy()
        {
            return ProxyFunction(Argument1.Value);
        }

        protected override void AttachCore()
        {
            Argument1.Attach();
            Argument1.ValueChanged += ArgumentChanged;
            base.AttachCore();
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            Argument1.ValueChanged -= ArgumentChanged;
            Argument1.Detach();
        }

        public override bool IsParameterFree
        {
            get { return Argument1.IsParameterFree; }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableStaticProxyCall<T1, TResult>(ProxyFunction, Argument1.ApplyParameters(parameters));
        }
    }

    internal sealed class ObservableStaticProxyCall<T1, T2, TResult> : ObservableStaticProxyCallBase<TResult>
    {
        public ObservableStaticProxyCall(MethodCallExpression node, ObservableExpressionBinder binder, MethodInfo proxyMethod)
            : this((Func<T1, T2, INotifyValue<TResult>>)proxyMethod.CreateDelegate(typeof(Func<T1, T2, INotifyValue<TResult>>)),
                binder.VisitObservable<T1>(node.Arguments[0]),
                binder.VisitObservable<T2>(node.Arguments[1])) { }

        public ObservableStaticProxyCall(Func<T1, T2, INotifyValue<TResult>> proxyFunction, IObservableExpression<T1> arg1, IObservableExpression<T2> arg2)
        {
            if (proxyFunction == null) throw new ArgumentNullException("proxyFunction");
            if (arg1 == null) throw new ArgumentNullException("arg1");
            if (arg2 == null) throw new ArgumentNullException("arg2");

            ProxyFunction = proxyFunction;
            Argument1 = arg1;
            Argument2 = arg2;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        public IObservableExpression<T2> Argument2 { get; private set; }

        public Func<T1, T2, INotifyValue<TResult>> ProxyFunction { get; private set; }

        protected override INotifyValue<TResult> CreateProxy()
        {
            return ProxyFunction(Argument1.Value, Argument2.Value);
        }

        protected override void AttachCore()
        {
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.Attach();
            Argument1.ValueChanged += argChanged;
            Argument2.Attach();
            Argument2.ValueChanged += argChanged;
            base.AttachCore();
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.ValueChanged -= argChanged;
            Argument1.Detach();
            Argument2.ValueChanged -= argChanged;
            Argument2.Detach();
        }

        public override bool IsParameterFree
        {
            get { return Argument1.IsParameterFree && Argument2.IsParameterFree; }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableStaticProxyCall<T1, T2, TResult>(ProxyFunction, Argument1.ApplyParameters(parameters), Argument2.ApplyParameters(parameters));
        }
    }

    internal sealed class ObservableStaticProxyCall<T1, T2, T3, TResult> : ObservableStaticProxyCallBase<TResult>
    {
        public ObservableStaticProxyCall(MethodCallExpression node, ObservableExpressionBinder binder, MethodInfo proxyMethod)
            : this((Func<T1, T2, T3, INotifyValue<TResult>>)proxyMethod.CreateDelegate(typeof(Func<T1, T2, T3, INotifyValue<TResult>>)),
                binder.VisitObservable<T1>(node.Arguments[0]),
                binder.VisitObservable<T2>(node.Arguments[1]),
                binder.VisitObservable<T3>(node.Arguments[2])) { }

        public ObservableStaticProxyCall(Func<T1, T2, T3, INotifyValue<TResult>> proxyFunction,
            IObservableExpression<T1> arg1,
            IObservableExpression<T2> arg2,
            IObservableExpression<T3> arg3)
        {
            if (proxyFunction == null) throw new ArgumentNullException("proxyFunction");
            if (arg1 == null) throw new ArgumentNullException("arg1");
            if (arg2 == null) throw new ArgumentNullException("arg2");
            if (arg3 == null) throw new ArgumentNullException("arg3");

            ProxyFunction = proxyFunction;
            Argument1 = arg1;
            Argument2 = arg2;
            Argument3 = arg3;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        public IObservableExpression<T2> Argument2 { get; private set; }

        public IObservableExpression<T3> Argument3 { get; private set; }

        public Func<T1, T2, T3, INotifyValue<TResult>> ProxyFunction { get; private set; }

        protected override INotifyValue<TResult> CreateProxy()
        {
            return ProxyFunction(Argument1.Value, Argument2.Value, Argument3.Value);
        }

        protected override void AttachCore()
        {
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.Attach();
            Argument1.ValueChanged += argChanged;
            Argument2.Attach();
            Argument2.ValueChanged += argChanged;
            Argument3.Attach();
            Argument3.ValueChanged += argChanged;
            base.AttachCore();
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.ValueChanged -= argChanged;
            Argument1.Detach();
            Argument2.ValueChanged -= argChanged;
            Argument2.Detach();
            Argument3.ValueChanged -= argChanged;
            Argument3.Detach();
        }

        public override bool IsParameterFree
        {
            get { return Argument1.IsParameterFree && Argument2.IsParameterFree && Argument3.IsParameterFree; }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableStaticProxyCall<T1, T2, T3, TResult>(ProxyFunction, Argument1.ApplyParameters(parameters), Argument2.ApplyParameters(parameters), Argument3.ApplyParameters(parameters));
        }
    }

    internal sealed class ObservableStaticProxyCall<T1, T2, T3, T4, TResult> : ObservableStaticProxyCallBase<TResult>
    {
        public ObservableStaticProxyCall(MethodCallExpression node, ObservableExpressionBinder binder, MethodInfo proxyMethod)
            : this((Func<T1, T2, T3, T4, INotifyValue<TResult>>)proxyMethod.CreateDelegate(typeof(Func<T1, T2, T3, T4, INotifyValue<TResult>>)),
                binder.VisitObservable<T1>(node.Arguments[0]),
                binder.VisitObservable<T2>(node.Arguments[1]),
                binder.VisitObservable<T3>(node.Arguments[2]),
                binder.VisitObservable<T4>(node.Arguments[3])) { }

        public ObservableStaticProxyCall(Func<T1, T2, T3, T4, INotifyValue<TResult>> proxyFunction,
            IObservableExpression<T1> arg1,
            IObservableExpression<T2> arg2,
            IObservableExpression<T3> arg3,
            IObservableExpression<T4> arg4)
        {
            if (proxyFunction == null) throw new ArgumentNullException("proxyFunction");
            if (arg1 == null) throw new ArgumentNullException("arg1");
            if (arg2 == null) throw new ArgumentNullException("arg2");
            if (arg3 == null) throw new ArgumentNullException("arg3");
            if (arg4 == null) throw new ArgumentNullException("arg4");

            ProxyFunction = proxyFunction;
            Argument1 = arg1;
            Argument2 = arg2;
            Argument3 = arg3;
            Argument4 = arg4;
        }

        public IObservableExpression<T1> Argument1 { get; private set; }

        public IObservableExpression<T2> Argument2 { get; private set; }

        public IObservableExpression<T3> Argument3 { get; private set; }

        public IObservableExpression<T4> Argument4 { get; private set; }

        public Func<T1, T2, T3, T4, INotifyValue<TResult>> ProxyFunction { get; private set; }

        protected override INotifyValue<TResult> CreateProxy()
        {
            return ProxyFunction(Argument1.Value, Argument2.Value, Argument3.Value, Argument4.Value);
        }

        protected override void AttachCore()
        {
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.Attach();
            Argument1.ValueChanged += argChanged;
            Argument2.Attach();
            Argument2.ValueChanged += argChanged;
            Argument3.Attach();
            Argument3.ValueChanged += argChanged;
            Argument4.Attach();
            Argument4.ValueChanged += argChanged;
            base.AttachCore();
        }

        protected override void DetachCore()
        {
            base.DetachCore();
            var argChanged = new EventHandler<ValueChangedEventArgs>(ArgumentChanged);
            Argument1.ValueChanged -= argChanged;
            Argument1.Detach();
            Argument2.ValueChanged -= argChanged;
            Argument2.Detach();
            Argument3.ValueChanged -= argChanged;
            Argument3.Detach();
            Argument4.ValueChanged -= argChanged;
            Argument4.Detach();
        }

        public override bool IsParameterFree
        {
            get
            {
                return Argument1.IsParameterFree
                    && Argument2.IsParameterFree
                    && Argument3.IsParameterFree
                    && Argument4.IsParameterFree;
            }
        }

        public override IObservableExpression<TResult> ApplyParameters(IDictionary<string, object> parameters)
        {
            return new ObservableStaticProxyCall<T1, T2, T3, T4, TResult>(ProxyFunction,
                Argument1.ApplyParameters(parameters),
                Argument2.ApplyParameters(parameters),
                Argument3.ApplyParameters(parameters),
                Argument4.ApplyParameters(parameters));
        }
    }
}
